#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include "lora_tp2210.h"

#include "stm32l4xx_hal.h"


struct tp2210 {
    tp2210_write_fn  write;
    tp2210_millis_fn millis;
    tp2210_lock_fn   lock;
    tp2210_unlock_fn unlock;
    void            *lock_user;
    tp2210_events_t  ev;

    // rx line assembly
    char  line[TP2210_RXLINE_SZ];
    size_t line_len;

    // sync command state
    volatile int  wait_result;         // 1=waiting,0=OK,-1=ERROR,-2=timeout
    uint32_t      wait_deadline;
    char         *resp_buf;
    size_t        resp_cap;
    size_t        resp_len;
};

size_t tp2210_sizeof(void){ return sizeof(tp2210_t); }

// static void _lock(tp2210_t *c){ if(c->lock) c->lock(c->lock_user); }
// static void _unlock(tp2210_t *c){ if(c->unlock) c->unlock(c->lock_user); }
// static uint32_t _now(tp2210_t *c){ return c->millis? c->millis():0; }

int tp2210_init(tp2210_t **out, void *storage, const tp2210_conf_t *conf){
    if(!out || !storage || !conf || !conf->write || !conf->millis) return -1;
    tp2210_t *c = (tp2210_t*)storage;
    memset(c,0,sizeof(*c));
    c->write  = conf->write;
    c->millis = conf->millis;
    c->lock   = conf->lock;
    c->unlock = conf->unlock;
    c->lock_user = conf->lock_user;
    c->ev = conf->events;
    c->wait_result = 0;
    *out = c; return 0;
}

// helper: append to response buffer if present
static void rb_append(tp2210_t *c, const char *s){
    if(!c->resp_buf || !s) return;
    size_t n = strlen(s);
    if(c->resp_len + n + 1 > c->resp_cap) n = (c->resp_cap>c->resp_len+1? c->resp_cap - c->resp_len - 1:0);
    memcpy(c->resp_buf + c->resp_len, s, n);
    c->resp_len += n;
    c->resp_buf[c->resp_len] = '\0';
}

// parse hex to bytes, returns length or <0
static int hex2bin(const char *hex, uint8_t *out, size_t cap){
    size_t n = strlen(hex);
    if(n%2) return -1;
    size_t bytes = n/2; if(bytes>cap) return -2;
    for(size_t i=0;i<bytes;i++){
        char c1=hex[2*i], c2=hex[2*i+1];
        int v1 = isdigit((unsigned char)c1)? c1-'0' : (toupper((unsigned char)c1)-'A'+10);
        int v2 = isdigit((unsigned char)c2)? c2-'0' : (toupper((unsigned char)c2)-'A'+10);
        if(v1<0||v1>15||v2<0||v2>15) return -3;
        out[i] = (uint8_t)((v1<<4)|v2);
    }
    return (int)bytes;
}

// emit NNMI with hex payload string -> bytes buffer on stack
static void emit_nnmi(tp2210_t *c, const char *esn_opt, const char *len_s, const char *hex){
    if(!c->ev.on_nnmi) return;
    int len = atoi(len_s);
    size_t hexlen = strlen(hex);
    // trust len but cap by hex length/2
    size_t cap = (hexlen/2);
    if(len<0 || (size_t)len>cap) len = (int)cap;
    uint8_t tmp[255]; if((size_t)len>sizeof(tmp)) len = sizeof(tmp);
    // best-effort decode
    int got = hex2bin(hex, tmp, len);
    if(got<0) return;
    c->ev.on_nnmi(c, esn_opt && *esn_opt? esn_opt:NULL, (uint16_t)got, tmp);
}

// Handle a fully assembled line (without CRLF)
static void handle_line(tp2210_t *c, const char *ln){
    if(!ln) return;
    // Collect to waiting buffer when a sync cmd is pending
    if(c->wait_result==1 && c->resp_buf){ rb_append(c, ln); rb_append(c, "\n"); }

    if(strcmp(ln, "OK")==0){ if(c->wait_result==1) c->wait_result=0; return; }
    if(strncmp(ln, "ERROR",5)==0){ if(c->wait_result==1) c->wait_result=-1; return; }

    // +NNMI variants
    if(strncmp(ln, "+NNMI:",6)==0){
        // try parse with ESN: "+NNMI:ESN,len,hex"
        const char *p = ln+6; // after prefix
        // strip blanks
        while(*p==' ') p++;
        const char *c1 = strchr(p, ',');
        if(c1){
            // maybe ESN first?
            if((size_t)(c1-p) >= 8){
                // Peek next token
                const char *c2 = strchr(c1+1, ',');
                if(c2){
                    // token1 could be ESN if it's hex-ish and len token numeric
                    int looks_len = 1; for(const char *q=c1+1;q<c2;q++) if(!isdigit((unsigned char)*q)) {looks_len=0;break;}
                    if(looks_len){
                        char esn[32]={0}; size_t esnlen = (size_t)(c1-p); if(esnlen>31) esnlen=31; memcpy(esn,p,esnlen); esn[esnlen]='\0';
                        char len_s[8]={0}; size_t l = (size_t)(c2-(c1+1)); if(l>7) l=7; memcpy(len_s,c1+1,l); len_s[l]='\0';
                        const char *hex = c2+1;
                        emit_nnmi(c, esn, len_s, hex);
                        return;
                    }
                }
            }
        }
        // else fallback: "+NNMI:len,hex"
        const char *comma = strchr(p, ',');
        if(comma){
            char len_s[8]={0}; size_t l=(size_t)(comma-p); if(l>7) l=7; memcpy(len_s,p,l); len_s[l]='\0';
            emit_nnmi(c, NULL, len_s, comma+1);
            return;
        }
    }

    // DEV ATTACH / REJECT
    if(strncmp(ln, "DEV ATTACH:",11)==0 && c->ev.on_dev_attach){
        // format: DEV ATTACH: ESN,logic,lp,proto
        const char *p = ln+11; while(*p==' ') p++;
        char esn[32]={0}, logic[8]={0}; int lp=0, proto=0;
        sscanf(p, "%31[^,],%7[^,],%d,%d", esn, logic, &lp, &proto);
        c->ev.on_dev_attach(c, esn, logic, lp, proto);
        return;
    }
    if(strncmp(ln, "DEV REJECT:",11)==0 && c->ev.on_dev_reject){
        const char *p=ln+11; while(*p==' ') p++;
        char esn[32]={0}; sscanf(p, "%31s", esn);
        c->ev.on_dev_reject(c, esn); return;
    }
    if(strncmp(ln, "+DEV:",5)==0 && c->ev.on_dev_request){
        // +DEV: ESN,proto,reason,ul,dl,txpwr
        const char *p=ln+5; while(*p==' ') p++;
        char esn[32]={0}; int proto=0,reason=0,ul=0,dl=0,tx=0;
        sscanf(p, "%31[^,],%d,%d,%d,%d,%d", esn,&proto,&reason,&ul,&dl,&tx);
        c->ev.on_dev_request(c, esn, proto, reason, ul, dl, tx); return;
    }

    if(strncmp(ln, "CDS:",4)==0 && c->ev.on_cds_state){ int s=atoi(ln+4); c->ev.on_cds_state(c, s); return; }

    if(strncmp(ln, "CHANNEL ",8)==0 && c->ev.on_channel_chosen){
        // "CHANNEL 43:505710,-118"
        int id=0,noisedbm=0; int khz=0; sscanf(ln+8, "%d:%d,%d", &id, &khz, &noisedbm);
        c->ev.on_channel_chosen(c, id, khz, noisedbm); return;
    }

    if(strncmp(ln, "+ATCMD:",7)==0 && c->ev.on_atcmd_result){
        // +ATCMD:ESN,.... whole text line after comma
        const char *p=ln+7; while(*p==' ') p++;
        const char *comma = strchr(p, ',');
        if(comma){ char esn[32]={0}; size_t n=(size_t)(comma-p); if(n>31) n=31; memcpy(esn,p,n); esn[n]='\0';
            c->ev.on_atcmd_result(c, esn, comma+1); return; }
    }

    if(strncmp(ln, "+ROUTECMD=",10)==0 && c->ev.on_routecmd_result){
        // "+ROUTECMD=ESN,cmd,ret"
        const char *p=ln+10; char esn[32]={0}; int cmd=0,ret=0;
        sscanf(p, "%31[^,],%d,%d", esn,&cmd,&ret);
        c->ev.on_routecmd_result(c, esn, cmd, ret); return;
    }

    if(c->ev.on_line) c->ev.on_line(c, ln);
}

size_t tp2210_input_bytes(tp2210_t *ctx, const uint8_t *data, size_t len){
    size_t consumed=0;
    for(size_t i=0;i<len;i++){
        char ch=(char)data[i]; consumed++;
        if(ch=='\r') continue; // skip CR, look for LF
        if(ch=='\n'){
            ctx->line[ctx->line_len]='\0';
            handle_line(ctx, ctx->line);
            ctx->line_len=0; continue;
        }
        if(ctx->line_len+1 < sizeof(ctx->line)) ctx->line[ctx->line_len++]=ch;
        else {
            // overflow -> reset line
            ctx->line_len=0;
        }
    }
    return consumed;
}

// low-level send string
static int _send(tp2210_t *c, const char *s) {
    size_t n=strlen(s);
    int w=c->write((const uint8_t*)s,n);
    return (w==(int)n)?0:-1;
}

//向网关发送命令
int tp2210_cmd(tp2210_t *c, uint32_t timeout_ms, char *resp, size_t cap, const char *fmt, ...) {
    char buf[TP2210_TXBUF_SZ];
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);

    if (n <= 0) {
        return -1; // 格式化失败
    }

    // 直接发送（底层串口发送函数，你自己实现）
    return _send(c, buf);
}

int tp2210_get_esn(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+EUI?\r\n");
}
int tp2210_get_ver(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+VER?\r\n");
}
int tp2210_reboot(tp2210_t *c) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+REBOOT\r\n");
}
int tp2210_factory_reset(tp2210_t *c) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+DEF\r\n");
}
int tp2210_freq_get(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+FREQ?\r\n");
}
int tp2210_freq_set(tp2210_t *c, int index) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+FREQ=%d\r\n", index);
}

int tp2210_pwr_get(tp2210_t *c, int *dbm) {
    char b[64] = {0};
    int rc = tp2210_cmd(c, 0, b, sizeof(b), "AT+PWR?\r\n");
    if (rc == 0 && dbm) {
        // parse like "0 dbm\nOK"
        char *sp = strchr(b, ' ');
        if (sp) {
            *sp = '\0';
            *dbm = atoi(b);
        }
    }
    return rc;
}

int tp2210_pwr_set(tp2210_t *c, int dbm) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+PWR=%d\r\n", dbm);
}

int tp2210_symbol_get(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+SYMBOL?\r\n");
}

int tp2210_symbol_set(tp2210_t *c, int dl_idx, int ul_idx) {
    if (ul_idx < 0)
        return tp2210_cmd(c, 0,NULL, 0, "AT+SYMBOL=%02d\r\n", dl_idx);
    return tp2210_cmd(c, 0,NULL, 0, "AT+SYMBOL=%02d,%02d\r\n", dl_idx, ul_idx);
}

int tp2210_dev_query(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+DEV?\r\n");
}

int tp2210_dev_add(tp2210_t *c, const char *esn, int lp_mode) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+DEV=0,%s,%d\r\n", esn, lp_mode);
}

int tp2210_dev_del(tp2210_t *c, const char *esn) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+DEV=1,%s\r\n", esn);
}

int tp2210_dev_allow(tp2210_t *c, const char *esn, int lp_mode) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+DEV=2,%s,%d\r\n", esn, lp_mode);
}

int tp2210_dev_reject(tp2210_t *c, const char *esn) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+DEV=3,%s\r\n", esn);
}

int tp2210_channel_query(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+CHANNEL?\r\n");
}
int tp2210_channel_auto(tp2210_t *c, int enable) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+CHANNEL=0,%d\r\n", !!enable);
}
int tp2210_channel_scan(tp2210_t *c) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+CHANNEL=1\r\n");
}

static void bin2hex(const uint8_t *in, size_t n, char *out) {
    static const char *H = "0123456789ABCDEF";
    for (size_t i = 0; i < n; i++) {
        out[2 * i] = H[(in[i] >> 4) & 0xF];
        out[2 * i + 1] = H[in[i] & 0xF];
    }
    out[2 * n] = '\0';
}

int tp2210_send(tp2210_t *c, const char *esn, const uint8_t *data, size_t len, uint32_t to_ms) {
    if (len > 255) len = 255;
    char hex[512];
    bin2hex(data, len, hex);
    if (!esn || (esn[0] == '0' && esn[1] == '\0'))
        return tp2210_cmd(c, to_ms,NULL, 0, "AT+SEND=0,%u,%s\r\n",
                                                                     (unsigned) len, hex);
    return tp2210_cmd(c, to_ms,NULL, 0, "AT+SEND=%s,%u,%s\r\n", esn, (unsigned) len, hex);
}

int tp2210_atcmd_to_node(tp2210_t *c, const char *esn, const char *at, uint32_t to_ms) {
    return tp2210_cmd(c, to_ms,NULL, 0, "AT+ATCMD=%s,%s\r\n", esn, at);
}

int tp2210_time_get(tp2210_t *c, char *out, size_t cap) {
    return tp2210_cmd(c, 0, out, cap, "AT+TIME?\r\n");
}

int tp2210_time_set(tp2210_t *c, int y, int m, int d, int hh, int mm, int ss) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+TIME=%d,%d,%d,%d,%d,%d\r\n", y, m, d, hh, mm, ss);
}

int tp2210_cellid_get(tp2210_t *c, int *out) {
    char b[64] = {0};
    int rc = tp2210_cmd(c, 0, b, sizeof(b), "AT+CELLID?\r\n");
    if (rc == 0 && out) {
        const char *p = strchr(b, ':');
        if (p) *out = atoi(p + 1);
    }
    return rc;
}

int tp2210_cellid_set(tp2210_t *c, int id) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+CELLID=%d\r\n", id);
}

int tp2210_route_enable(tp2210_t *c, const char *esn, int en, int ch) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+ROUTE=%s,1,%d,%d\r\n", esn, !!en, ch);
}

int tp2210_route_beacon(tp2210_t *c, const char *esn, int rounds) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+ROUTE=%s,8,%d\r\n", esn, rounds);
}

int tp2210_route_delete(tp2210_t *c, const char *esn) {
    return tp2210_cmd(c, 0,NULL, 0, "AT+ROUTE=%s,3\r\n", esn);
}
